﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Numerics;

namespace OverSATbLib
{
    public class Tuple
    {
        private static int IdCtr = 0;

        public static TupleEquality TupleEquality = new TupleEquality();

        public int Id { get; set; }

        public List<Assignment> AssignItems = new List<Assignment>();

        public Tuple()
        {
            Id = IdCtr++;
        }

        public Tuple(int nameid, uint value) : this()
        {
            AssignItems.Add(new Assignment { NameId = nameid, Value = value, Index = nameid });
        }

        public Tuple(int nameid0, uint value0, int nameid1, uint value1) : this()
        {
            AssignItems.Add(new Assignment { NameId = nameid0, Value = value0, Index = nameid0 });
            AssignItems.Add(new Assignment { NameId = nameid1, Value = value1, Index = nameid1 });
        }

        public Tuple(int nameid0, uint value0, int nameid1, uint value1, int nameid2, uint value2) : this()
        {
            AssignItems.Add(new Assignment { NameId = nameid0, Value = value0, Index = nameid0 });
            AssignItems.Add(new Assignment { NameId = nameid1, Value = value1, Index = nameid1 });
            AssignItems.Add(new Assignment { NameId = nameid2, Value = value2, Index = nameid2 });
        }

        public Tuple(int nameid0, uint value0, int nameid1, uint value1, int nameid2, uint value2, int nameid3, uint value3) : this()
        {
            AssignItems.Add(new Assignment { NameId = nameid0, Value = value0, Index = nameid0 });
            AssignItems.Add(new Assignment { NameId = nameid1, Value = value1, Index = nameid1 });
            AssignItems.Add(new Assignment { NameId = nameid2, Value = value2, Index = nameid2 });
            AssignItems.Add(new Assignment { NameId = nameid3, Value = value3, Index = nameid3 });
        }

        public Tuple(int nameid0, uint value0, int nameid1, uint value1, int nameid2, uint value2, int nameid3, uint value3, int nameid4, uint value4) : this()
        {
            AssignItems.Add(new Assignment { NameId = nameid0, Value = value0, Index = nameid0 });
            AssignItems.Add(new Assignment { NameId = nameid1, Value = value1, Index = nameid1 });
            AssignItems.Add(new Assignment { NameId = nameid2, Value = value2, Index = nameid2 });
            AssignItems.Add(new Assignment { NameId = nameid3, Value = value3, Index = nameid3 });
            AssignItems.Add(new Assignment { NameId = nameid4, Value = value4, Index = nameid4 });
        }

        public Tuple(int nameid0, uint value0, int nameid1, uint value1, int nameid2, uint value2, int nameid3, uint value3, int nameid4, uint value4, int nameid5, uint value5) : this()
        {
            AssignItems.Add(new Assignment { NameId = nameid0, Value = value0, Index = nameid0 });
            AssignItems.Add(new Assignment { NameId = nameid1, Value = value1, Index = nameid1 });
            AssignItems.Add(new Assignment { NameId = nameid2, Value = value2, Index = nameid2 });
            AssignItems.Add(new Assignment { NameId = nameid3, Value = value3, Index = nameid3 });
            AssignItems.Add(new Assignment { NameId = nameid4, Value = value4, Index = nameid4 });
            AssignItems.Add(new Assignment { NameId = nameid5, Value = value5, Index = nameid5 });
        }

        public Tuple(int nameid0, uint value0, int nameid1, uint value1, int nameid2, uint value2, int nameid3, uint value3, int nameid4, uint value4, int nameid5, uint value5, int nameid6, uint value6) : this()
        {
            AssignItems.Add(new Assignment { NameId = nameid0, Value = value0, Index = nameid0 });
            AssignItems.Add(new Assignment { NameId = nameid1, Value = value1, Index = nameid1 });
            AssignItems.Add(new Assignment { NameId = nameid2, Value = value2, Index = nameid2 });
            AssignItems.Add(new Assignment { NameId = nameid3, Value = value3, Index = nameid3 });
            AssignItems.Add(new Assignment { NameId = nameid4, Value = value4, Index = nameid4 });
            AssignItems.Add(new Assignment { NameId = nameid5, Value = value5, Index = nameid5 });
            AssignItems.Add(new Assignment { NameId = nameid6, Value = value6, Index = nameid6 });
        }

        public Tuple(int nameid0, uint value0, int nameid1, uint value1, int nameid2, uint value2, int nameid3, uint value3, int nameid4, uint value4, int nameid5, uint value5, int nameid6, uint value6, int nameid7, uint value7) : this()
        {
            AssignItems.Add(new Assignment { NameId = nameid0, Value = value0, Index = nameid0 });
            AssignItems.Add(new Assignment { NameId = nameid1, Value = value1, Index = nameid1 });
            AssignItems.Add(new Assignment { NameId = nameid2, Value = value2, Index = nameid2 });
            AssignItems.Add(new Assignment { NameId = nameid3, Value = value3, Index = nameid3 });
            AssignItems.Add(new Assignment { NameId = nameid4, Value = value4, Index = nameid4 });
            AssignItems.Add(new Assignment { NameId = nameid5, Value = value5, Index = nameid5 });
            AssignItems.Add(new Assignment { NameId = nameid6, Value = value6, Index = nameid6 });
            AssignItems.Add(new Assignment { NameId = nameid7, Value = value7, Index = nameid7 });
        }

        public int GetRank()
        {
            return AssignItems.Min(x => x.Index);
        }

        public BigInteger GetBits()
        {
            BigInteger bi = 0;
            BigInteger bx = 0;
            foreach (var a in AssignItems)
            {
                if (a.Value > 0)
                {
                    bx = 1;
                    bx <<= a.Index;
                    bi |= bx;
                }
            }
            return bi;
        }


        public BigInteger GetMask()
        {
            BigInteger bi = 0;
            BigInteger bx = 0;
            foreach (var a in AssignItems)
            {
                bx = 1;
                bx <<= a.Index;
                bi |= bx;
            }
            return bi;
        }

        public BigInteger GetNotMask(int maxLevels)
        {
            BigInteger bx = 0;
            BigInteger by = 1;
            for (int i = 0; i < maxLevels; i++)
            {
                if (!AssignItems.Any( a => a.Index == i ))
                {
                    by = 1;
                    by <<= i;
                    bx |= by;
                }
            }
            string test = bx.ToBinaryString();
            return bx;
        }

        public override string ToString()
        {
            return string.Join(", ", AssignItems.Select(a => string.Format("{0}={1}", a.NameId, a.Value)).ToArray());
        }

        public string ToBitString()
        {
            return string.Join(" ", AssignItems.Select(a => string.Format("{0}", (a.Value == 0) ? -a.NameId : a.NameId)).ToArray());
        }

        public static MergeBitTupleResponse MergeBitTuple( Tuple srcA, Tuple srcB )
        {
            MergeBitTupleResponse mbt = new MergeBitTupleResponse();

            List<Assignment> first = null, second = null;
            Tuple  mergeTuple = new Tuple();
            Tuple removeTuple = null;

            if (srcA.AssignItems[0].Index >= srcB.AssignItems[0].Index)
            {
                first = srcA.AssignItems;
                second = srcB.AssignItems;
                removeTuple = srcA;
            }
            else
            {
                second = srcA.AssignItems;
                first = srcB.AssignItems;
                removeTuple = srcB;
            }

            int commonCount = 0;

            int firstIdx = 0;
            int secondIdx = 0;

            bool IsIdentity = true;

            while (first[firstIdx].Index > second[secondIdx].Index && firstIdx < first.Count)
            {
                mergeTuple.AssignItems.Add(first[firstIdx]);
                firstIdx++;
            }

            if (firstIdx == first.Count)
            {
                mbt.Optimized = false;
                return mbt;
            }

            bool canChopFirstTail = true;

            while (firstIdx < first.Count && secondIdx < second.Count)
            {
                if (first[firstIdx].Index == second[secondIdx].Index)
                {
                    if (first[firstIdx].Value == second[secondIdx].Value)
                    {
                        mergeTuple.AssignItems.Add(first[firstIdx]);
                    }
                    else
                    {
                        canChopFirstTail = false;
                        IsIdentity = false;
                    }
                    firstIdx++;
                    secondIdx++;
                    commonCount++;
                }
                else
                {
                    mbt.Optimized = false;
                    return mbt;
                }
            }

            if (secondIdx < second.Count)
            {
                mbt.Optimized = false;
                return mbt;
            }

            if (firstIdx < first.Count)
            {
                IsIdentity = false;
                if (!canChopFirstTail)
                {
                    while (firstIdx < first.Count)
                    {
                        mergeTuple.AssignItems.Add(first[firstIdx]);
                        firstIdx++;
                    }
                }
            }

            if (IsIdentity)
            {
                mbt.Optimized = false;
            }
            else if (mergeTuple.AssignItems.Count > 0)
            {
                mbt.Optimized = true;
                mbt.TuplesToAdd.Add(mergeTuple);
                mbt.TuplesToRemove.Add(removeTuple);
            }
            else
            {
                mbt.Optimized = true;
                mbt.EntireSolutionIsFalse = true;
                mbt.TuplesToRemove.Add(removeTuple);
                if (removeTuple == srcA)
                {
                    mbt.TuplesToRemove.Add(srcB);
                }
                else
                {
                    mbt.TuplesToRemove.Add(srcA);
                }
            }
            return mbt;
        }

        public Tuple Intersection( Tuple t )
        {
            Tuple r = new Tuple();

            var bailCount = t.AssignItems
                .Join(AssignItems,
                    a => a.NameId,
                    b => b.NameId,
                    (a, b) => new { a, b }
                ).Where(x => x.a.Value != x.b.Value)
                .Count();

            if (bailCount > 0)
                return null;

            r.AssignItems = AssignItems
                .Union(t.AssignItems, Assignment.AssignmentEquality)
                .ToList();
            
            return r;
        }


        /// <summary>
        /// CancelTuple returns the tuples if they did not cancelled out, otherwise, they 
        /// are placed in cancelledTuples list.
        /// </summary>
        /// <param name="t"></param>
        /// <param name="cancelledTuples"></param>
        /// <returns></returns>
        public List<Tuple> CancelTuple( Tuple t, List<Tuple> cancelledTuples )
        {
            List<Tuple> returnTuples = new List<Tuple>();
            Tuple r = new Tuple();

            var joinItems = t.AssignItems
                .Join(AssignItems,
                    a => a.NameId,
                    b => b.NameId,
                    (a, b) => new { a, b }
                ).ToList();

            if (joinItems.Count == t.AssignItems.Count && AssignItems.Count == joinItems.Count)
            {
                var newList = joinItems
                    .Where(a => a.a.Value == a.b.Value)
                    .Select( a => new Assignment
                    {
                        Index = a.a.Index,
                        Name = a.a.Name,
                        NameId = a.a.NameId,
                        Value = a.a.Value
                    })
                    .ToList();

                if (newList.Count != t.AssignItems.Count)
                {
                    r.AssignItems.AddRange(newList);
                    returnTuples.Add(r);

                    cancelledTuples.Add(this);
                    cancelledTuples.Add(t);
                }
            }
            return returnTuples;
        }

        /// <summary>
        /// ExactOr returns a list of Tuples that will satisfy this | t for deleted rows but without 
        /// any overlapping tuples.
        /// </summary>
        /// <param name="addtuple"></param>
        /// <returns></returns>
        public List<Tuple> ExactOr( Tuple addtuple, List<Tuple> cancelledTuples )
        {
            List<Tuple> results = new List<Tuple>();

            var startItems = addtuple.AssignItems.Except(AssignItems, Assignment.AssignmentEquality).ToArray();
            var baseItems = AssignItems.Except(startItems).ToArray();
            int bits = baseItems.Count();

            int comb = 1 << bits;

            if (bits > 0)
            {
                for (int i = 0; i < comb; i++)
                {
                    Tuple nt = new Tuple();
                    int k = 1;

                    for (int j = 0; j < startItems.Length; j++)
                    {
                        var eai = startItems[j];
                        var nas = new Assignment
                        {
                            NameId = eai.NameId,
                            Name = eai.Name,
                            Index = eai.Index,
                            Value = eai.Value
                        };
                        nt.AssignItems.Add(nas);
                    }

                    for (int j = 0; j < bits; j++)
                    {
                        var eai = baseItems[j];
                        var nas = new Assignment
                        {
                            NameId = eai.NameId,
                            Name = eai.Name,
                            Index = eai.Index,
                            Value = (i & k) > 0 ? 1u : 0u
                        };
                        nt.AssignItems.Add(nas);
                        k <<= 1;
                    }

                    if (addtuple.Intersection(nt) != null && Intersection(nt) == null)
                    {
                        results.Add(nt);
                    }
                }

                cancelledTuples.Add(this);
                cancelledTuples.Add(addtuple);
            }

            return results;
        }

    }

    public class MergeBitTupleResponse
    {
        public bool Optimized { get; set; }

        public bool EntireSolutionIsFalse { get; set; }

        public List<Tuple> TuplesToRemove
        {
            get; private set;
        } = new List<Tuple>();

        public List<Tuple> TuplesToAdd
        {
            get; private set;
        }  = new List<Tuple>();
    }

    public static class ListExtensions
    {
        public static string ToString(this IEnumerable<Tuple> tuples)
        {
            var sresult = string.Join("\r\n", tuples.Select(a => a.ToString()).ToArray());
            return sresult;
        }

        public static string ToPrettyString(this IEnumerable<Tuple> tuples)
        {
            var sresult = string.Join("\r\n", tuples.Select(a => a.ToString()).ToArray());
            return sresult;
        }

    }

    public class TupleEquality : IEqualityComparer<Tuple>
    {
        public bool Equals(Tuple x, Tuple y)
        {
            var sameVarCount = x.AssignItems.Join(y.AssignItems, xv => xv.NameId, yv => yv.NameId, (xv, yv) => new { xv, yv })
                .Where(a => a.xv.Value == a.yv.Value)
                .Count();

            bool result = (sameVarCount == x.AssignItems.Count && sameVarCount == y.AssignItems.Count);

            return result;
        }

        public int GetHashCode(Tuple obj)
        {
            int k = 1;
            foreach (var item in obj.AssignItems)
            {
                k ^= item.NameId;
                k ^= (int)item.Value;
            }
            return k;
        }
    }

}
